package de.uni_stuttgart.ipvs.ids.clocks;

import java.text.DecimalFormat;

/**
 * This Class simulates a hardware clock.
 * 
 * @author Team 3/4 Elkhan Dadashov, Ahmed Labib, Dhaval A Shah, Patrick Stortz
 */
public class Clock extends Thread {

	/** The amount of physical time between two ticks in milliseconds */
	private final long incrementTime;
	/** The constant drift of the clock */
	private final double drift;
	/** The current time of the clock in milliseconds */
	private long counter;
	/** The name to identify the clock */
	private final String name;

	/**
	 * Creates and starts a simulation of a hardware clock. <br>
	 * Note: No need to start the Thread, already done with calling the
	 * constructor!
	 * 
	 * @param name
	 *            The name to identify the clock.
	 * @param incrementTime
	 *            The amount of physical time between two ticks in milliseconds.
	 * @param drift
	 *            The constant drift of the clock.
	 */
	public Clock(String name, long incrementTime, double drift) {
		super("Clock " + name);
		this.counter = 0L;
		this.incrementTime = incrementTime;
		this.drift = drift;
		this.name = name;
		this.start();
	}

	/**
	 * Returns the current time of the clock in milliseconds.
	 * 
	 * @return the current time of the clock in milliseconds.
	 */
	public synchronized long getTime() {
		return this.counter;
	}

	/**
	 * Sets the time of the clock in milliseconds.
	 * 
	 * @param time
	 *            the time to set in milliseconds.
	 */
	public synchronized void setTime(long time) {

		// set the time
		this.counter = time;

		// check if this clock is currently "within" a tick
		// means the call is coming not from this thread itself!
		if (this.getState() == State.TIMED_WAITING) {
			// interrupt the clock to start a new tick
			this.interrupt();
		}
	}

	@Override
	public void run() {

		// define the time to sleep
		// (the incrementTime influenced by the constant drift)
		long sleep = (long) ((1.0 + this.drift) * this.incrementTime);

		// loop and increment the clock
		System.out.println(getName() + " | Clock ticking...");// DEBUG
		while (true) {
			try {
				// wait for the above defined time
				Thread.sleep(sleep);
				// increment the clock only by the supposed increment time
				// (without considering the drift)
				this.setTime(this.getTime() + this.incrementTime);
			} catch (InterruptedException e) {
				// we got interrupted while sleeping
				// most probably happened because another thread called
				// setTime() and we need to start a new tick!
				continue;
			}
		}
	}

	/** Format used for millisecondsToString() */
	private static final DecimalFormat df = new DecimalFormat("0.000");

	/**
	 * Transforms a long value in milliseconds to a String
	 * 
	 * @param time
	 *            the long value in milliseconds
	 * @return A string either formatted as milliseconds or seconds
	 */
	public static String millisecondsToString(long time) {
		if (time < 1000) {
			return time + "ms";
		} else {
			return df.format((time / 1000.0)) + "s";
		}
	}
}
